//
// Created by bangsun on 2022/10/15.
//

#include <signal.h>
#include <assert.h>

#include "include/inner_gmp.h"
#include "include/switch.h"
#include "include/util.h"
#include "include/time_sched.h"

# define SWITCH_TO(new_esp) __asm__ __volatile__( \
"push %%ebp\n"   \
"movl %%esp,%%ebp\n"   \
"push %%edi\n"                          \
"push %%esi\n"                          \
"push %%ebx\n"                          \
"push %%edx\n"                          \
"push %%ecx\n"                          \
"push %%eax\n"                          \
"pushfl\n"                              \
                 \
"movl %0,%%esp\n"/*switch to the actual entry*/ \
"popfl\n"                               \
"popl %%eax\n"                          \
"popl %%edx\n"                          \
"popl %%ecx\n"                          \
"popl %%ebx\n"                          \
"popl %%esi\n"                          \
"popl %%edi\n"                          \
"popl %%ebp\n"                          \
"ret\n"                                 \
::"m"(new_esp):)

# define SWITCH_TO_LOAD_OLD(new_esp, old_esp) __asm__ __volatile__( \
"push %%ebp\n"   \
"movl %%esp,%0\n"  /*fetch old esp*/  \
"push %%edi\n"                          \
"push %%esi\n"                          \
"push %%ebx\n"                          \
"push %%edx\n"                          \
"push %%ecx\n"                          \
"push %%eax\n"                          \
"pushfl\n"                              \
                 \
"movl %1,%%esp\n"/*switch to the actual entry*/ \
"popfl\n"                               \
"popl %%eax\n"                          \
"popl %%edx\n"                          \
"popl %%ecx\n"                          \
"popl %%ebx\n"                          \
"popl %%esi\n"                          \
"popl %%edi\n"                          \
"popl %%ebp\n"                          \
"ret\n"                                 \
:"=m"(old_esp):"m"(new_esp):)


#define LOAD_RET_ADDR(cur_esp) asm volatile("leal 36(%%esp),%0":"=c"(cur_esp)::)

#define LOAD_ESP(cur_esp) asm volatile("movl %%esp,%0":"=m"(cur_esp)::)

#define RESTORE_ESP(cur_esp) asm volatile("movl %0,%%esp"::"m"(cur_esp):)

#define SAVE(cur_esp) asm volatile( \
"push %%ebp\n"                          \
"push %%edi\n"                          \
"push %%esi\n"                          \
"push %%ebx\n"                          \
"push %%edx\n"                          \
"push %%ecx\n"                          \
"push %%eax\n"                          \
"pushfl\n"                           \
"movl %%esp,%0"\
:"=m"(cur_esp)::)

#define INIT_RESTORE(new_esp) asm volatile( \
"movl %0,%%esp\n"\
"popfl\n"                               \
"popl %%eax\n"                          \
"popl %%edx\n"                          \
"popl %%ecx\n"                          \
"popl %%ebx\n"                          \
"popl %%esi\n"                          \
"popl %%edi\n"                          \
"popl %%ebp\n"                          \
"ret\n"                         \
::"m"(new_esp):)

#define YIELD_RESTORE(new_esp) asm volatile( \
"movl %0,%%esp\n"\
"popfl\n"                               \
"popl %%eax\n"                          \
"popl %%edx\n"                          \
"popl %%ecx\n"                          \
"popl %%ebx\n"                          \
"popl %%esi\n"                          \
"popl %%edi\n"                          \
"popl %%ebp\n"                          \
::"m"(new_esp):)


PRIVATE void exit_abnormally();


PRIVATE void actual_entry(Loutine *loutine, void *arg);

void kill_loutine(Loutine *loutine) {
    loutine->state = KILLED_STATE;
}

void init_loutine(Loutine *loutine, void (*entry)(void *), void *arg) {


    loutine->stack = (WORD_PTR) malloc(STACK_SIZE * WORD_SIZE);
    assert(loutine->stack);
    loutine->state = INIT_STATE;
    loutine->entry = entry;
    loutine->arg = arg;
    loutine->time_slot = INIT_TIME_SLOT_SIZE;
    loutine->stacktop = loutine->stack + (STACK_SIZE - ESP_MOVE_OFFSET) * WORD_SIZE;
    loutine->to_kill = NULL;

    WORD *stack = (WORD *) loutine->stack;

    // | <self area> |eflags|eax|edx|ecx|ebx|esi|edi|ebp|<eip>(prev)
    //               -> stack index increase
    //                            stack grow direction <-

    // nonsense initialization
    stack[STACK_SIZE - EFLAGS_OFFSET] = 0; // esp/stacktop stays here
    stack[STACK_SIZE - EAX_OFFSET] = 0;
    stack[STACK_SIZE - EDX_OFFSET] = 0;
    stack[STACK_SIZE - ECX_OFFSET] = 0;
    stack[STACK_SIZE - EBX_OFFSET] = 0;
    stack[STACK_SIZE - ESI_OFFSET] = 0;
    stack[STACK_SIZE - EDI_OFFSET] = 0;
    stack[STACK_SIZE - EBP_OFFSET] = 0;
    // store the actual_entry
    stack[STACK_SIZE - RETADDR_OFFSET] = (WORD_PTR) actual_entry;

    // below is the stack frame of actual_entry
    //actual_entry shall not return normally
    stack[STACK_SIZE - TERMINATE_ENTRY_OFFSET] = (WORD_PTR) exit_abnormally;
    // argument list of actual entry
    stack[STACK_SIZE - ENTRY_LOUTINE_ARG_OFFSET] = (WORD_PTR) loutine;
    stack[STACK_SIZE - ENTRY_PARAM_ARG_OFFSET] = (WORD_PTR) arg;

    loutine->id = register_task(loutine);
}

Loutine *create_loutine(void (*entry)(void *), void *arg) {
    Loutine *loutine = malloc(sizeof(Loutine));
    init_loutine(loutine, entry, arg);
    return loutine;
}


void switch_to_loutine(Loutine *next) {
    if (next->state == INIT_STATE) {
        INIT_RESTORE(next->stacktop);// with ret instruction
    } else {
        printf("next state:%d\n", next->state);
        YIELD_RESTORE(next->stacktop);//without ret instruction
    }
}


void reschedule_gmp(Loutine *curr, Loutine *next) {
    curr->state = READY_STATE;
    SAVE(curr->stacktop);
    switch_to_loutine(next);
}

void init_loutine_environment() {
    init_gmp_env();
}


void actual_entry(Loutine *loutine, void *arg) {
    // free the previous loutine if needed
    if (loutine->to_kill != NULL) {
        free_loutine(loutine->to_kill);
        loutine->to_kill = NULL;
    }

    loutine->state = RUNNING_STATE;
    loutine->entry(arg);
    loutine->state = DONE_STATE;

    M *self = find_cur_m();
    assert(self);
    Loutine *next = find_next_g_or_even_p(self,true);
    next->to_kill = loutine;

    if (next->state == INIT_STATE) {
        INIT_RESTORE(next->stacktop);// with ret instruction
    } else {
        printf("next state:%d\n", next->state);
        YIELD_RESTORE(next->stacktop);//without ret instruction
    }

    // find next?
}

void exit_abnormally() {
    printf("sched function shall not reach\n");
    assert(0);
}


void free_loutine(void *loutine) {
    free((void *) ((Loutine *) loutine)->stack);
    free(loutine);
}

